/*
 * CCVisu is a tool for visual graph clustering
 * and general force-directed graph layout.
 * This file is part of CCVisu. 
 * 
 * Copyright (C) 2005-2010  Dirk Beyer
 * 
 * CCVisu is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * CCVisu is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with CCVisu; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Please find the GNU Lesser General Public License in file
 * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt
 * 
 * Dirk Beyer    (firstname.lastname@uni-passau.de)
 * University of Passau, Bavaria, Germany
 */

package ccvisu;

import java.awt.Color;
import java.io.PrintWriter;

import ccvisu.Options.Option;

/*****************************************************************
 * Writer for layouts in SVG format (Scalable Vector Graphs, XML, W3C).
 * @version  $Revision: 1.31 $; $Date: 2007/12/15 01:20:51 $
 * @author   Dirk Beyer
 *****************************************************************/
public class WriterDataGraphicsSVG extends WriterDataGraphics {

  /** used to build unique id for the edges */
  private int edgeNumber = 0;

  /**
   * Constructor.
   */
  public WriterDataGraphicsSVG(PrintWriter out, GraphData graph, Options opt) {
    super(out, graph, opt);
  }

  /*****************************************************************
   * Writes the layout in graphics format SVG.
   *****************************************************************/
  @Override
  public void write() {
    int size = (int) (1000 * Option.scalePos.getFloat());

    // Header.
    out
        .print("<?xml version=\"1.0\" standalone=\"no\"?>"
               + WriterData.endl
               + " "
               + WriterData.endl
               + "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\""
               + WriterData.endl
               + "  \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">"
               + WriterData.endl
               + " "
               + WriterData.endl
               + "<!-- Generated by "
               + Options.toolDescription()
               + WriterData.endl
               + "     "
               + Options.currentDateTime()
               + " -->"
               + WriterData.endl
               + " "
               + WriterData.endl
               + "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\""
               + WriterData.endl
               + "     xmlns:xlink=\"http://www.w3.org/1999/xlink\""
               + WriterData.endl
               + "     width=\"100%\" height=\"100%\""
               + WriterData.endl
               + "     viewBox=\"0 0 "
               + size
               + " "
               + size
               + "\">"
               + WriterData.endl
               + " "
               + WriterData.endl
               + " <title>Visualization "
               + options.inputName
               + "</title>"
               + WriterData.endl
               + " "
               + WriterData.endl
               + " <script><![CDATA[ "
               + WriterData.endl
               + "     "
               + WriterData.endl
               + "  /* "
               + WriterData.endl
               + "   * Returns the innermost SVG object that triggered the event "
               + WriterData.endl
               + "   * and that has a non-null id attribute. This can be either the event's "
               + WriterData.endl
               + "   * target itself, or its parent node, or the first ancestor of the target "
               + WriterData.endl
               + "   * with a non-null id. "
               + WriterData.endl
               + "   * "
               + WriterData.endl
               + "   * Input Parameters: "
               + WriterData.endl
               + "   *   evt - The JavaScript object describing the triggering event. "
               + WriterData.endl
               + "   * Return Value: "
               + WriterData.endl
               + "   *   The target node or first target's ancestor with a non-null id. "
               + WriterData.endl
               + "   */ "
               + WriterData.endl
               + "  function get_target(evt) { "
               + WriterData.endl
               + "    var target = evt.target; "
               + WriterData.endl
               + "    while (target && !target.getAttribute('id')) { "
               + WriterData.endl
               + "      target = target.parentNode; "
               + WriterData.endl
               + "    } "
               + WriterData.endl
               + "    return target; "
               + WriterData.endl
               + "  }  "
               + WriterData.endl
               + "     "
               + WriterData.endl
               + "  /* "
               + WriterData.endl
               + "   * Adds or removes a duplicate of the text object that  "
               + WriterData.endl
               + "   * corresponds to the circle that was clicked on / pointed to: "
               + WriterData.endl
               + "   * The circle node is the evt.target. "
               + WriterData.endl
               + "   * If the text node is already there, the node gets removed. "
               + WriterData.endl
               + "   * Otherwise the text node is cloned. The cloned node's id is changed. "
               + WriterData.endl
               + "   * The cloned node is appended to the 'contents' group. "
               + WriterData.endl
               + "   * "
               + WriterData.endl
               + "   * Input Parameters: "
               + WriterData.endl
               + "   *   evt -     JavaScript object describing the triggering event. "
               + WriterData.endl
               + "   *   postfix - String to distinguish between click and move. "
               + WriterData.endl
               + "   */ "
               + WriterData.endl
               + "  function annot_toggle(evt, postfix) { "
               + WriterData.endl
               + "    // Retrieve node of object that was clicked on. "
               + WriterData.endl
               + "    var target = get_target(evt); "
               + WriterData.endl
               + "    var svgdoc = target.ownerDocument; "
               + WriterData.endl
               + "      "
               + WriterData.endl
               + "    // Retrieve annotation node. "
               + WriterData.endl
               + "    var annotNode = svgdoc.getElementById(target.getAttribute('id') "
               + WriterData.endl
               + "                                          + '__text' + postfix); "
               + WriterData.endl
               + "    // Check whether annotation is already set. "
               + WriterData.endl
               + "    if (annotNode) { "
               + WriterData.endl
               + "      // Remove object's node from its group node. "
               + WriterData.endl
               + "      var groupnode = annotNode.parentNode; "
               + WriterData.endl
               + "      groupnode.removeChild (annotNode); "
               + WriterData.endl
               + "    } else { "
               + WriterData.endl
               + "      // Clone the text object and set its attributes. "
               + WriterData.endl
               + "      var text = svgdoc.getElementById(target.getAttribute('id') + '__text'); "
               + WriterData.endl
               + "      annotNode = text.cloneNode(true); "
               + WriterData.endl
               + "      annotNode.setAttribute('id', target.getAttribute('id') + '__text' + postfix); "
               + WriterData.endl
               + "      // Retrieve the node for 'contents' group. "
               + WriterData.endl
               + "      var contents = svgdoc.getElementById('contents'); "
               + WriterData.endl
               + "      // Insert the cloned object into the contents group node. "
               + WriterData.endl
               + "      contents.parentNode.appendChild(annotNode); "
               + WriterData.endl
               + "      window.status = target.getAttribute('id'); "
               + WriterData.endl + "    } " + WriterData.endl + "  } "
               + WriterData.endl + "    " + WriterData.endl + " ]]></script> "
               + WriterData.endl + WriterData.endl);

    // Body.
    writeGraphicsLayout(graph.vertices, graph.edges, size);

    // Footer.
    out.print("</svg> " + WriterData.endl);
  }

  /**
   * Writes a vertex in SVG format.
   * @param curVertex  The vertex object, to access vertex attributes.
   * @param xPos       x coordinate of the vertex.
   * @param yPos       y coordinate of the vertex.
   * @param zPos       z coordinate of the vertex.
   * @param radius     Radius of the vertex.
   */
  @Override
  public void writeVertex(GraphVertex curVertex, int xPos, int yPos, int zPos,
                          int radius) {

    String name = curVertex.name;
    //remove double quote
    if (name.startsWith("\"") && name.endsWith("\"")) {
      name = name.substring(1, name.length() - 1);
    }

    // Prepare color string for vertex.
    // Convert to hex RGB value and get rid of the alpha value.
    String colorStr =
                      Integer
                          .toHexString(curVertex.color.getRGB() & 0x00FFFFFF);
    // Fill up to 6 digits.
    colorStr = "000000" + colorStr;
    colorStr = colorStr.substring(colorStr.length() - 6, colorStr.length());

    // Prepare color string for annotation text.
    Color textColor = new Color(0xffffffff - options.backColor.get().getRGB());
    // Convert to hex RGB value and get rid of the alpha value.
    String textColStr = Integer.toHexString(textColor.getRGB() & 0x00FFFFFF);
    // Fill up to 6 digits.
    textColStr = "000000" + textColStr;
    textColStr =
                 textColStr.substring(textColStr.length() - 6, textColStr
                     .length());

    // Write the definition for the annotation.
    out.print(" <defs>" + "  <text" + " id=\"" + name + "__text\"" + " x=\""
              + (xPos + radius + 3) + "\"" + " y=\"" + (yPos + 3) + "\""
              + " style=\"font-family: helvetica, sans-serif;font-size:"
              + Option.fontSize.getInt() + "pt;fill:#" + textColStr + "\"> "
              + name + (curVertex.hasSelfLoop ? "-LOOP" : "") + " </text>"
              + " </defs> " + WriterData.endl);

    // Write graphical objects.
    out.print(" <g id=\"contents\"> " + WriterData.endl);

    // Write vertex.
    String strokeString = "";
    if (Option.blackCircle.getBool()) {
      strokeString = " stroke=\"black\"";
    }
    //link information
    if (Option.openURL.getBool()) {
      out.println("    <a xlink:href=\"" + name + "\">");
    }

    out.print("  <circle" + " id=\"" + name + "\"" + " cx=\"" + xPos + "\""
              + " cy=\"" + yPos + "\"" + " r=\"" + radius + "\"" + " fill=\"#"
              + colorStr + "\"" + strokeString
              + " onmouseover=\"annot_toggle(evt, '_move')\""
              + " onmouseout=\"annot_toggle(evt, '_move')\""
              + " onclick=\"annot_toggle(evt, '_click')\"" + " /> "
              + WriterData.endl);

    if (Option.openURL.getBool()) {
      out.println("    </a>");
    }

    // Write annotation, only if required.
    if (curVertex.showName) {
      out.print("  <text" + " id=\"" + name + "__text_click\"" + " x=\""
                + (xPos + radius + 3) + "\"" + " y=\"" + (yPos + 3) + "\""
                + " style=\"font-family: helvetica, sans-serif;font-size:"
                + Option.fontSize.getInt() + "pt;fill:#" + textColStr + "\"> "
                + name + " </text> " + WriterData.endl);
    }

    out.print(" </g> " + WriterData.endl);
  }

  /**
   * Writes an edge.
   * @param edge        an edge in graph.edges
   * @param xPos1       x coordinate of the first point.
   * @param yPos1       y coordinate of the first point.
   * @param zPos1       z coordinate of the first point.
   * @param xPos2       x coordinate of the second point.
   * @param yPos2       y coordinate of the second point.
   * @param zPos2       z coordinate of the second point.
   */
  @Override
  public void writeEdge(GraphEdge edge, int xPos1, int yPos1, int zPos1,
                        int xPos2, int yPos2, int zPos2) {

    String edgeName = edge.relName;

    //Prepare color string for edge and annotation text.
    // Move this to initialization in main perhaps:
    //   Color textColor = new Color(0xffffffff - options.backColor.get().getRGB());
    // Convert to hex RGB value and get rid of the alpha value.
    String colStr = Integer.toHexString(edge.color.getRGB() & 0x00FFFFFF);
    // Fill up to 6 digits.
    colStr = "000000" + colStr;
    colStr = colStr.substring(colStr.length() - 6, colStr.length());

    //    Write the definition for the annotation.
    out.print(" <defs>" + "  <text" + " id=\"" + edgeName + edgeNumber
              + "__text\"" + " x=\""
              + ((xPos1 + xPos2) / 2 + Option.fontSize.getInt() + 3) + "\""
              + " y=\"" + ((yPos1 + yPos2) / 2 + Option.fontSize.getInt() + 3)
              + "\"" + " style=\"font-family: helvetica, sans-serif;font-size:"
              + Option.fontSize.getInt() + "pt;fill:#" + colStr + "\"> "
              + edgeName + " </text>" + " </defs> " + WriterData.endl);

    // Write graphical objects.
    out.print(" <g id=\"contents\" stroke=\"#" + colStr + "\"> "
              + WriterData.endl);

    // Draw the line.
    out
        .print("<line id=\"" + edgeName + edgeNumber
               + "\" style=\"stroke-width:1;fill:none;\" " + "y1=\"" + yPos1
               + "\" y2=\"" + yPos2 + "\" x1=\"" + xPos1 + "\" x2=\"" + xPos2
               + "\" " + " onmouseover=\"annot_toggle(evt, '_move')\""
               + " onmouseout=\"annot_toggle(evt, '_move')\""
               + " onclick=\"annot_toggle(evt, '_click')\" />"
               + WriterData.endl);

    // Draw the arrow head.
    int[] arr = paintArrow(xPos1, yPos1, xPos2, yPos2);

    out
        .print("<line id=\"" + edgeName + edgeNumber + "_tip1"
               + "\" style=\"stroke-width:1;fill:none;\" " + "y1=\"" + arr[1]
               + "\" y2=\"" + arr[3] + "\" x1=\"" + arr[0] + "\" x2=\""
               + arr[2] + "\" " + " onmouseover=\"annot_toggle(evt, '_move')\""
               + " onmouseout=\"annot_toggle(evt, '_move')\""
               + " onclick=\"annot_toggle(evt, '_click')\" />"
               + WriterData.endl);

    out
        .print("<line id=\"" + edgeName + edgeNumber + "_tip2"
               + "\" style=\"stroke-width:1;fill:none;\" " + "y1=\"" + arr[5]
               + "\" y2=\"" + arr[7] + "\" x1=\"" + arr[4] + "\" x2=\""
               + arr[6] + "\" " + " onmouseover=\"annot_toggle(evt, '_move')\""
               + " onmouseout=\"annot_toggle(evt, '_move')\""
               + " onclick=\"annot_toggle(evt, '_click')\" />"
               + WriterData.endl);

    // Write annotation, only if required.
    if (Option.annotAll.getBool()) {
      out.print("  <text" + " id=\"" + edgeName + edgeNumber + "__text\""
                + " x=\""
                + ((xPos1 + xPos2) / 2 + Option.fontSize.getInt() + 3) + "\""
                + " y=\""
                + ((yPos1 + yPos2) / 2 + Option.fontSize.getInt() + 3) + "\""
                + " style=\"font-family: helvetica, sans-serif;font-size:"
                + Option.fontSize.getInt() + "pt\"> " + edgeName + " </text> "
                + WriterData.endl);
    }

    out.print(" </g> " + WriterData.endl);

    ++edgeNumber;
  }

};
